package com.abou.aop.logs.impl;

import java.util.Arrays;
import java.util.List;
import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import com.abou.aop.logs.LogAspect;

/**
 * The implementation class of the aspect enabling to log a method name and its
 * inputs when entering a method, and to log a method name and the returned
 * value when returning from the method.
 * 
 * @author khalid
 */
@Aspect
public class LogAspectImpl implements LogAspect {
  
  /**
   * The logger
   */
  private static final Logger LOGGER = Logger.getLogger(LogAspectImpl.class);
  
  /**
   * The execution order for the aspect
   */
  private static final int ORDER = 11;
  
  /**
   * The empty constructor of the class
   */
  public LogAspectImpl() {

    super();
  }
  
  /***
   * @see com.abou.aop.logs.LogAspect#log(org.aspectj.lang.ProceedingJoinPoint)
   */
  @Around("execution(* com.francetelecom.malicli.mac.manager.impl.*ManagerImpl.*(..)) "
      + "|| execution(* com.francetelecom.malicli.mac.manager.helper.impl.*HelperImpl.*(..)) "
      + "|| execution(* com.francetelecom.malicli.mac.connector.dao.impl.*DaoImpl.*(..)) "
      + "|| execution(* com.francetelecom.malicli.mac.connector..*.*ConnectorImpl.*(..)) "
      + "|| execution(* com.francetelecom.malicli.mac.connector..*.*StubsManagerImpl.*(..)) "
      + "|| execution(* com.francetelecom.malicli.mac.mvc.controller..*.*Controller.*(..)) ")
  public Object log(ProceedingJoinPoint pjp) throws Throwable {

    if (LOGGER.isDebugEnabled()) {
      
      // logging method name and inputs
      StringBuilder builder = new StringBuilder();
      builder.append("Entering method \"");
      builder.append(getMethodSimpleName(pjp));
      builder.append(" \nInputs are : \n");
      printInputs(pjp.getArgs(), builder);
      Logger curLogger =
          Logger.getLogger(pjp.getSourceLocation().getWithinType());
      curLogger.debug(builder.toString());
    }
    
    // executing the method
    Object retVal = pjp.proceed();
    
    if (LOGGER.isDebugEnabled()) {
      
      // logging method name and returned value
      StringBuilder builder = new StringBuilder();
      builder.append("Exiting method \"");
      builder.append(getMethodSimpleName(pjp));
      builder.append("\nReturned value is : ");
      builder.append(getStringRepresentation(retVal));
      Logger curLogger =
          Logger.getLogger(pjp.getSourceLocation().getWithinType());
      curLogger.debug(builder.toString());
    }
    
    return retVal;
  }
  
  /**
   * Prints the string representation of all the objects of the passed array
   * into the passed builder.
   * 
   * @param inputs An array of objects
   * @param builder The builder into which strings will be written
   */
  private static void printInputs(Object[] inputs, StringBuilder builder) {

    for (int i = 0; i < inputs.length; i++) {
      
      builder.append("\targ[");
      builder.append(i);
      builder.append("] : ");
      builder.append(getStringRepresentation(inputs[i]));
      builder.append("\n");
    }
  }
  
  /**
   * Returns a string representation of the passed object.
   * 
   * @param obj An object
   * @return A string
   */
  private static String getStringRepresentation(Object obj) {

    String rep = null;
    
    if (obj == null) {
      
      rep = "null";
      
    } else {
      
      // checking the type of the object
      Class<?> clazz = obj.getClass();
      
      if (clazz.isArray()) {
        
        // as the object is an array, its is converted into a list
        List<Object> list = Arrays.asList(obj);
        rep = list.toString();
        
      } else {
        
        /*
         * the object is not an array, its representation is taken from its
         * toString() method.
         */
        rep = obj.toString();
      }
    }
    
    return rep;
  }
  
  /**
   * Computes the name of the method according to the following structure
   * SimpleClassName.methodName.
   * 
   * @param pjp A join point
   * @return A simple name for the method
   */
  private static String getMethodSimpleName(ProceedingJoinPoint pjp) {

    return pjp.getSourceLocation().getWithinType().getSimpleName() + "."
        + pjp.getSignature().getName();
  }
  
  /**
   * @see org.springframework.core.Ordered#getOrder()
   */
  public int getOrder() {

    return ORDER;
  }
}
